iT邦幫忙

2021 iThome 鐵人賽

DAY 17
1
Modern Web

Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架系列 第 17

[Day 17] 新功能的測試,檢驗不應該存在的資料

  • 分享至 

  • xImage
  •  

自動測試時除了檢查加入新資料,有時我們也會希望檢查舊資料是否成功地被移除。

今天我們用一個新的功能,來展示如何針對不應該存在的資料進行檢查。

新功能測試

我們多加一個新的函數,可以接收 listOf(tag),來更新 user 所有的 tag

重點是,我們這邊更新的內容,不僅僅是針對單一個 user

我們還接受傳 listOf(user),一次更新所有的 user.tags

fun updateUsersTags(users: List<User>, tags: List<Tag>) {
}

撰寫測試案例

像昨天一樣,我們在 tests/kotlin/ 資料夾內,建立 UpdateUsersTagsKtTest.kt 檔案,並加上幾個測試案例

internal class UpdateUsersTagsKtTest {
	fun `測試單一全新用戶加上標籤`() {
	Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", driver = "org.h2.Driver")  
	    transaction {
			SchemaUtils.create(Users)  
			SchemaUtils.create(Tags)  
			SchemaUtils.create(UsersTags)  
			val testUser = User.new {  
				name = "TestUser"  
			}  
			val testTag = Tag.new {  
				name = "TestTag"  
			}  
			updateUsersTags(listOf(testUser), listOf(testTag))  
			assertEquals(
				listOf(testTag),
				testUser.tags.toList()
			)  
		}  
	}
}

運作測試看看,我們發現測試並沒有通過

expected:<[Tag@2380d06e]> but was:<[]>
Expected :[Tag@2380d06e]
Actual   :[]

這是理所當然的,因為我們的 updateUsersTags() 裡面還是空的

我們根據昨天的 userAddTag() 稍微調整一下寫法,看看能不能通過

fun updateUsersTags(users: List<User>, tags: List<Tag>) {  
    transaction {
        users.forEach {  
            it.tags = SizedCollection(it.tags.toList() + tags)  
        }  
    }
}

重新運作測試,就可以通過了。

我們多加另一個測試的案例 測試多個用戶加上標籤

@Test  
fun `測試多個用戶加上標籤`() {  
	Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testUser2 = User.new {  
 			name = "TestUse2"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		updateUsersTags(listOf(testUser, testUser2), listOf(testTag))  
        assertEquals(listOf(testTag), testUser.tags.toList())  
        assertEquals(listOf(testTag), testUser2.tags.toList())}  
}

這個測試也可以成功通過

為求測試的完整,我們再加上 測試已有標籤用戶更動新標籤

@Test  
fun `測試已有標籤用戶更動新標籤`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		val testTag2 = Tag.new {  
 			name = "TestTag2"  
 		}  
 		testUser.tags = SizedCollection(listOf(testTag))  
        updateUsersTags(listOf(testUser), listOf(testTag2))  
        assertEquals(listOf(testTag2), testUser.tags.toList())  
    }  
}

執行之後,我們發現以下錯誤

expected:<[Tag@641aca5a]> but was:<[Tag@60c77761, Tag@641aca5a]>
Expected :[Tag@641aca5a]
Actual   :[Tag@60c77761, Tag@641aca5a]

這是怎麼回事呢?不是只是拿昨天的程式做了一點調整嗎?

和我們的需求邏輯比較之後,我們會發現,這是因為昨天的邏輯是「新增」,但是今天需求的邏輯是「更新」導致的。

我們需要再調整一下我們的程式

fun updateUsersTags(users: List<User>, tags: List<Tag>) {  
    transaction {  
		users.forEach {  
			it.tags = SizedCollection(tags)  
		}  
 	}
 }

這樣的話,我們的 測試已有標籤用戶更動新標籤 就會通過了。

我們可以再增加一些測試案例,像是

  • 測試多個已有標籤用戶更動新標籤
  • 測試多個已有標籤用戶更動多個新標籤
@Test  
fun `測試多個已有標籤用戶更動新標籤`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
	transaction {  
         SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
             name = "TestUser"
        }
        val testUser2 = User.new {  
            name = "TestUser2"
        }
        val testTag = Tag.new {  
            name = "TestTag"  
        }  
        val testTag2 = Tag.new {  
            name = "TestTag2"  
        }  
        testUser.tags = SizedCollection(listOf(testTag))  
        testUser2.tags = SizedCollection(listOf(testTag))  
        updateUsersTags(listOf(testUser, testUser2), listOf(testTag2))  
        assertEquals(listOf(testTag2), testUser.tags.toList())  
        assertEquals(listOf(testTag2), testUser2.tags.toList())  
    }  
}  
  
@Test  
fun `測試多個已有標籤用戶更動多個新標籤`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testUser2 = User.new {  
 			name = "TestUser2"  
 		}  
 		val oldTag = Tag.new {  
 			name = "oldTag"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		val testTag2 = Tag.new {  
 			name = "TestTag2"  
 		}  
 		testUser.tags = SizedCollection(listOf(oldTag))
        testUser2.tags = SizedCollection(listOf(oldTag)) 
		
        updateUsersTags(listOf(testUser, testUser2), listOf(testTag, testTag2))  
        assertEquals(listOf(testTag, testTag2), testUser.tags.toList())  
        assertEquals(listOf(testTag, testTag2), testUser2.tags.toList())  
    }  
}

這些測試都可以順利通過,代表我們的程式應該是沒有問題的。

不過,雖然程式本身沒有問題,但是剛剛「更新」和「新增」之間的邏輯區分,確實讓我們花了點時間確認,未來的工程師可能在這裡也會遇到類似的邏輯問題。

如果我們想要強調這段程式是「更新」而不是「新增」,自動測試也能幫助我們嗎?

增加協助我們釐清邏輯的測試案例

上面的問題,答案是:可以的!

我們可以加上一個案例 測試更新已有標籤用戶時應移除舊標籤。這樣一來,如果有人不小心將這段程式,改成沒有移除舊標籤的版本,他就會從錯誤訊息內,看到這個未通過的測試案例,進而提醒他應該要移除掉舊標籤。

測試更新已有標籤用戶時應移除舊標籤 這個測試案例,要強調「應移除舊標籤」,所以用之前的 assertEquals() 斷言是不夠的。

這邊我們要利用 org.junit.Assert.* 套件的 assertThat(),搭配上 org.hamcrest.CoreMatchers.* 套件的 not()hasItem(),來協助我們斷言舊標籤不存在於更新之後的 user.tags

我們來實作一下 測試更新已有標籤用戶時應移除舊標籤

fun `測試更新已有標籤用戶時應移除舊標籤`() {  
	Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val oldTag = Tag.new {  
 			name = "oldTag"  
 		}  
 		val newTag = Tag.new {  
 			name = "newTag"  
 		}  
 		testUser.tags = SizedCollection(listOf(oldTag))
		updateUsersTags(listOf(testUser), listOf(newTag))  
        assertThat(testUser.tags.toList(), not(hasItem(oldTag)))  
    }

這個測試案例運行通過之後,可以保證之後的修改,如果不小心在執行 updateUsersTags() 時,沒有移除舊標籤的話,可以在自動測試的階段就被發現到。


上一篇
[Day 16] 第一個和資料庫互動的測試
下一篇
[Day 18] 重構我們的測試程式碼
系列文
Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言